/**
 * Copyright 2010-2012 Jcorp
 */

#include "microscheme.h"
#include "circuitelement.h"
#include "performelement.h"
#include "configuredelement.h"

#include <QDebug>

MicroScheme::MicroScheme()
{
    author = "";
    description = "";
    canvasSize = QSize(80, 60);
    title = "";
    reset();
}

//Уничтожить все объекты схемы
MicroScheme::~MicroScheme()
{
    qDeleteAll(elements);
}

bool MicroScheme::checkElementName(const BaseElement &element, const QString &newname)
{
    int i = 0;
    int count = elements.count();
    while ((i < count) && !((elements.at(i)->name == newname) && (elements.at(i) != &element)))
        i++;
    return !(i < count);
}

//Обновить состояние схемы
void MicroScheme::reset()
{
    for (int i = 0; i < elements.count();i++)
    {
        elements.at(i)->reset();
    }
}

//Добавить элемент в схему
void MicroScheme::appendObject(BaseElement *element)
{
    elements.append(element);
}

//Удалить объект из схемы
void MicroScheme::disposeObject(BaseElement* element)
{
    if (element == NULL)
        return; //FIXIT add exception
    int index = elements.indexOf(element);
    if (index == 0)
    {
        //FIXIT add exception
    }
    else
    {
        BaseElement* el = elements.at(index);
        elements.remove(index);
        //Проверка типа
        CircuitElement* ce = dynamic_cast<CircuitElement*>(element);
        if (ce != NULL)
        {
            ce->disconnectAll();
        }
        delete el;
    }
}

BaseElement* MicroScheme::searchElementByName(const QString &name)
{
    int i = 0;
    int count = 0;
    while ((i < count) && (elements.at(i)->name != name))
        i++;
    if (i < count)
        return elements.at(i);
    else
        return NULL;
}

//Новое имя для элемента
QString MicroScheme::newElementName(const BaseElement &element)
{
    QString elementType = element.metaObject()->className();
    return newElementName(elementType);
}

//Новое имя по типу элемента
QString MicroScheme::newElementName(const QString &elementtype)
{
    QString newName = "";
    bool valid = false;
    int j = 1;
    while (!valid)
    {
        newName = elementtype + "_" + QString::number(j);
        int i = 0;
        int count = elements.count();
        while ((i < count) && !(elements.at(i)->name == newName))
            i++;
        if (i < count)
        {
            valid = false;
            j++;
        }
        else
            valid = true;
    }
    return newName;
}

//Запуск эмуляции схемы
void MicroScheme::run()
{
    //Переводим все элементы в начальное состояние
    reset();
    /*
    // Создаём фильтр с эффективными элементами
    QVector<IPerformElement*> pElements;
    // Создаём фильтр с неподключёнными входными пинами для подачи на них Initial Signal
    QVector<Pin*> notConnectedInputs;
    int elements_count = elements.count();
    for (int i = 0; i < elements_count; i++)
    {
        IPerformElement* pe = dynamic_cast<IPerformElement*>(elements.at(i));
        CircuitElement* ce = dynamic_cast<CircuitElement*>(elements.at(i));
        if (pe != NULL)
        {
            pElements.append(pe);
            if (ce != NULL)
            {
                //Ищем неподключенные входы
                for (int j = 0; j < ce->pins.count(); j++)
                {
                    Pin* pin = ce->pins.at(i);
                    if ((pin->friendPIN == NULL) &&
                        (pin->type == PinType_In))
                        notConnectedInputs.append(pin);
                }
            }
        }
    }
    //инициализация контейнеров для сбора информации
    for (int i = 0; i < watches.count(); i++)
        watches.at(i)->clear();

    int peCount = pElements.count();

    bool* marks = new bool[peCount];
    bool* sends = new bool[peCount];

    //цикл генерации сигналов
    for (uint tact = 0; tact < tactCount; tact++)
    {
        for (uint i = 0; i < ticPerTact; i++)
        {
            //генерируем новые сигналы
            for (int gi = 0; gi < generators.count();gi++)
                generators.at(gi)->generateSignal(i,tact);
            //генерируем сигналы на отключенных входных пинах
            for (int pi = 0; pi < notConnectedInputs.count(); pi++)
                notConnectedInputs.at(pi)->receive(initialSignal);
            bool perform = true;
            //только один эффективный send и perform на каждом такте
            for (int pi = 0; pi < peCount; pi++)
            {
                marks[pi] = false;
                sends[pi] = false;
            }

            //пока есть полезная работа, выполняем передачу и обработку сигналов в схеме
            while (perform)
            {
                perform = false;
                //цикл передачи данных
                for (int pi = 0; pi < peCount; pi++)
                    if (!sends[pi])
                    {
                        sends[pi] = pElements.at(pi)->sendSignals();
                    }
                //цикл выполнения работы
                for (int pi = 0; pi < peCount; pi++)
                    if (!marks[pi])
                    {
                        marks[pi] = pElements[pi]->performAction();
                        perform = perform | marks[pi];
                    }
            }

            //Секция сбора данных
            for (int j = 0; j < watches.count(); j++)
            {
                SignalContainer* wContainer = watches.at(j);
                Pin* source = dynamic_cast<Pin*>(wContainer->source);
                wContainer->addSignal(source->signal);
            }
        }
    }

    delete marks;
    delete sends;*/
}

MicroScheme* MicroScheme::loadFromNode(QDomDocument &domDoc)
{
    QDomNode rootNode;
    MicroScheme *res = new MicroScheme();
    rootNode = domDoc.namedItem(tr("root"));
    QDomNode documentElement = rootNode.namedItem(tr("document"));
    QDomNode canvasElement   = rootNode.namedItem(tr("canvas"));
    QDomNode elementsElement = rootNode.namedItem(tr("elements"));
    QDomNode connectsElement = rootNode.namedItem(tr("connects"));
    QDomNode watchesElement  = rootNode.namedItem(tr("watches"));

    return res;
}

QDomElement MicroScheme::saveToNode(MicroScheme* scheme, QDomDocument &domDoc)
{
    // Формируем документ
    QDomElement rootElement = domDoc.createElement("root");
    rootElement.setAttribute("xmlns","http://jcorp.ru/microscheme");

    // Свойства документа
    QDomElement documentElement = domDoc.createElement("document");
    XmlHelper::addProperty(documentElement, "SchemeName", scheme->title);

    // Свойства холста
    QDomElement canvasElement = domDoc.createElement("canvas");
    XmlHelper::addProperty(canvasElement, "Width", scheme->canvasSize.width());
    XmlHelper::addProperty(canvasElement, "Height", scheme->canvasSize.height());

    // Элементы
    QDomElement elementsElement = domDoc.createElement("elements");
    foreach (BaseElement* item, scheme->elements)
    {
        QDomElement itemElement = domDoc.createElement("item");
        itemElement.setAttribute("type",item->metaObject()->className());
        itemElement.setAttribute("name",item->name);
        itemElement.setAttribute("description",item->description);
        ConfiguredElement* configuredItem = dynamic_cast<ConfiguredElement*>(item);
        if (configuredItem)
            itemElement.setAttribute("config", configuredItem->getConfig());

        QDomElement drawsElement = domDoc.createElement("draws");
        // <draws>
        itemElement.appendChild(drawsElement);

        QDomElement propsElement = domDoc.createElement("props");
        item->saveData(propsElement);
        itemElement.appendChild(propsElement);

        CircuitElement* ceItem = dynamic_cast<CircuitElement*>(item);
        if (ceItem)
        {
            QDomElement pinsElement = domDoc.createElement("pins");
            foreach (Pin* currentPin, ceItem->pins)
            {
                QDomElement pinElement = domDoc.createElement("pin");
                pinElement.setAttribute("name",currentPin->name);
                pinElement.setAttribute("defvalue",currentPin->defaultSignal);
                pinsElement.appendChild(pinElement);
            }
            itemElement.appendChild(pinsElement);
        }
        elementsElement.appendChild(itemElement);
    }

    // Соединения
    QDomElement connectsElement = domDoc.createElement("connects");
    QList<Pin*> connectedPins;

    foreach (BaseElement* el, scheme->elements)
    {
        CircuitElement* ce = dynamic_cast<CircuitElement*>(el);
        if (ce != NULL)
        {
            foreach (Pin* p, ce->pins)
            {
                if(p->friendPIN != NULL)
                    connectedPins.append(p);
            }
        }
    }

    while (connectedPins.count() > 0)
    {
        Pin* p = connectedPins.at(0);
        Pin* friendPIN = p->friendPIN;

        QDomElement unite = domDoc.createElement("unite");
        unite.setAttribute("a", scheme->elements.indexOf(p->parentElement));
        unite.setAttribute("pina", p->name);
        unite.setAttribute("b", scheme->elements.indexOf(friendPIN->parentElement));
        unite.setAttribute("pinb", friendPIN->name);

        connectedPins.removeOne(p);
        connectedPins.removeOne(friendPIN);

        connectsElement.appendChild(unite);
    }



    // Добавляем главные элементы документа
    domDoc.appendChild(rootElement);
    rootElement.appendChild(documentElement);
    rootElement.appendChild(canvasElement);
    rootElement.appendChild(elementsElement);
    rootElement.appendChild(connectsElement);


    // Формируем служебную информацию
    QDomNode xmlNode = domDoc.createProcessingInstruction("xml",
                                "version=\"1.0\" encoding=\"UTF-8\"");
    domDoc.insertBefore(xmlNode, domDoc.firstChild());

    return rootElement;
}
